package org.liurb.springboot.starter.common.utils;

import cn.hutool.core.util.StrUtil;
import org.bouncycastle.util.encoders.Base64;
import org.springframework.util.CollectionUtils;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

/**
 * 常用加密工具类
 *
 * @Author Liurb
 * @Date 2022/11/28
 */
public class SignUtil {

    private static final int _rsa_keysize = 1024;

    private static final int MAX_ENCRYPT_BLOCK = (_rsa_keysize/8)-11;

    /**
     * 字典序排列拼接参数
     *
     * 默认不忽略空值情况
     *
     * 拼接格式为: name1=value1&name2=value2&...
     *
     * @param params
     * @return
     */
    public static String dictSortQuery(Map<String, Object> params) {

        return dictSortQuery(params, false);
    }

    /**
     * 字典序排列拼接参数
     *
     * 拼接格式为: name1=value1&name2=value2&...
     *
     * @param params
     * @param ignoreEmptyValue 是否忽略空值, true:忽略 false:不忽略
     * @return
     */
    public static String dictSortQuery(Map<String, Object> params, boolean ignoreEmptyValue) {

        Map<String, String> sortedParams = new TreeMap<>(Comparator.naturalOrder());
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String key = entry.getKey();

            String value = entry.getValue() == null ? null : entry.getValue().toString();
            sortedParams.put(key, value);

        }

        StringBuilder builder = new StringBuilder();

        if (!CollectionUtils.isEmpty(sortedParams)) {

            for (Map.Entry<String, String> entry : sortedParams.entrySet()) {

                String key = entry.getKey();
                Object value = entry.getValue();

                if (ignoreEmptyValue) {
                    if (value == null || StrUtil.isEmpty(value.toString())) {
                        continue;
                    }
                }

                builder.append(key);
                builder.append("=");
                builder.append(value);
                builder.append("&");
            }

            builder.deleteCharAt(builder.length() - 1);
        }

        return builder.toString();
    }

    /**
     * 使用私钥生成签名字符串
     *
     * @param content     签名内容
     * @param privateKey 私钥原始字符串
     * @param algorithms 算法/工作模式/填充方式.
     *
     * @return 签名结果字符串
     *
     * @throws Exception
     */
    public static String encryptPrivateByRSA(String content, String privateKey, String algorithms) throws Exception {

        Signature signature = Signature.getInstance(algorithms);
        signature.initSign(getPrivateKeyPKCS8(privateKey));
        signature.update(content.getBytes("UTF-8"));
        byte[] signed = signature.sign();

        String signText = Base64.toBase64String(signed);

        return signText;
    }

    /**
     * RSA私钥解密
     *
     * @param content 加密密文
     * @param privateKey 私钥
     * @param algorithms 算法/工作模式/填充方式
     * @return
     * @throws Exception
     */
    public static String decryptPrivateByRSA(String content, String privateKey, String algorithms) throws Exception {

        Cipher cipher = Cipher.getInstance(algorithms);
        cipher.init(Cipher.DECRYPT_MODE, getPrivateKeyPKCS8(privateKey));

        return Base64.toBase64String(cipher.doFinal(Base64.decode(content)));
    }

    /**
     * 使用公钥生成签名字符串
     *
     * @param content     签名内容
     * @param publicKey 公钥原始字符串
     * @param algorithms 算法/工作模式/填充方式.
     *
     * @return 签名结果字符串
     *
     * @throws Exception
     */
    public static String encryptPublicByRSA(String content, String publicKey, String algorithms) throws Exception {

        byte[] data = content.getBytes(StandardCharsets.UTF_8);

        return encryptPublicByRSA(data, publicKey, algorithms);
    }

    /**
     * 使用公钥生成签名字符串
     *
     * @param data     签名内容
     * @param publicKey 公钥原始字符串
     * @param algorithms 算法/工作模式/填充方式.
     *
     * @return 签名结果字符串
     *
     * @throws Exception
     */
    public static String encryptPublicByRSA(byte[] data, String publicKey, String algorithms) throws Exception {

        byte[] keyBytes = Base64.decode(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(algorithms);
        cipher.init(Cipher.ENCRYPT_MODE, publicK);

        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();

        return Base64.toBase64String(encryptedData);
    }

    /**
     * RSA验签
     *
     * @param content 验签内容
     * @param verifySign 验签签名
     * @param publicKey 公钥原始字符串
     * @param algorithms 算法/工作模式/填充方式.
     * @return
     * @throws Exception
     */
    public static boolean verifyByRSA(String content, String verifySign, String publicKey, String algorithms) throws Exception {
        Signature verify = Signature.getInstance(algorithms);
        verify.initVerify(getPublicKeyX509(publicKey));
        verify.update(content.getBytes("UTF-8"));
        return verify.verify(Base64.decode(verifySign));
    }

    /**
     * 将私钥字符串进行Base64 decode之后，生成PKCS #8标准的私钥
     *
     * @param privateKey 私钥原始字符串
     *
     * @return PKCS #8标准的私钥
     *
     * @throws Exception
     */
    public static PrivateKey getPrivateKeyPKCS8(String privateKey) throws Exception {
        if (StrUtil.isEmpty(privateKey)) {
            return null;
        }
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] decodedKey = Base64.decode(privateKey);
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
    }

    /**
     * 将公钥字符串进行Base64 decode之后，生成X509标准公钥
     *
     * @param publicKey 公钥原始字符串
     *
     * @return X509标准公钥
     *
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     */
    public static PublicKey getPublicKeyX509(String publicKey) throws InvalidKeySpecException,
            NoSuchAlgorithmException {
        if (StrUtil.isEmpty(publicKey)) {
            return null;
        }
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] decodedKey = Base64.decode(publicKey);
        return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
    }

}
